using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;

namespace ICommonLibrary.Net {

    public class ClientHttpRequest {

        private readonly WebRequest _connection;
        private Stream _os;

        private readonly Dictionary<string, string> _cookies = new Dictionary<string, string>();
        private string _rawCookies = "";

        private readonly string _boundary = "---------------------------" + RandomString() + RandomString() + RandomString();
        
        /// <summary>
        /// Creates a new multipart POST HTTP request.
        /// </summary>
        /// <param name="url"></param>
        public ClientHttpRequest(string url) {
            _connection = WebRequest.Create(url);
            _connection.Method = "POST";
            _connection.ContentType = "multipart/form-data; boundary=" + _boundary;
        }
        
        /// <summary>
        /// Adds a cookie to the requst
        /// </summary>
        /// <param name="name"></param>
        /// <param name="value"></param>
        public void SetCookie(string name, string value) {
            _cookies[name] = value;
        }
        
        /// <summary>
        /// Adds a cookie to the requst
        /// </summary>
        /// <param name="rawCookies"></param>
        public void SetCookies(string rawCookies) {
            _rawCookies = rawCookies ?? "";
            _cookies.Clear();
        }

        /// <summary>
        /// adds cookies to the request
        /// </summary>
        /// <param name="cookies"></param>
        public void SetCookies(Dictionary<string, string> cookies) {
            if (cookies == null) return;
            foreach (var cookie in cookies) {
                this._cookies[cookie.Key] = cookie.Value;

            }
        }

        /// <summary>
        /// Adds cookies to the request
        /// </summary>
        /// <param name="cookies">array of cookie names and values (cookies[2*i] is a name, cookies[2*i + 1] is a value)</param>
        public void SetCookies(string[] cookies) {
            if (cookies == null) return;
            for (int i = 0; i < cookies.Length - 1; i += 2) {
                SetCookie(cookies[i], cookies[i + 1]);
            }
        }
        
        /// <summary>
        /// Adds a string parameter to the request
        /// </summary>
        /// <param name="name"></param>
        /// <param name="value"></param>
        public void SetParameter(string name, string value) {
            Boundary();
            WriteName(name);
            Newline(); Newline();
            Writeln(value);
        }

        /// <summary>
        /// Adds a file parameter to the request
        /// </summary>
        /// <param name="name"></param>
        /// <param name="filename"></param>
        /// <param name="fileIn"></param>
        public void SetParameter(string name, string filename, Stream fileIn) {
            Boundary();
            WriteName(name);
            Write("; filename=\"");
            Write(filename);
            Write("\"");
            Newline();
            Write("Content-Type: ");
            string type = MimeType.GuessContentTypeFromName(filename);
            Writeln(type);
            Newline();
            IOUtil.CopyStream(fileIn, _os);
            Newline();
        }

        /// <summary>
        /// Adds a file parameter to the request.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="file"></param>
        public void SetParameter(string name, FileInfo file) {
            SetParameter(name, file.Name, file.OpenRead());
        }

        /// <summary>
        /// Adds a parameter to the request; if the parameter is a File, the file is uploaded, 
        /// otherwise the string value of the parameter is passed in the request 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="obj"></param>
        public void SetParameter(string name, Object obj) {
            if (obj is FileInfo) {
                SetParameter(name, (FileInfo)obj);
            } else {
                SetParameter(name, obj.ToString());
            }
        }

        /// <summary>
        /// Adds parameters to the request
        /// </summary>
        /// <param name="parameters">"name-to-value" map of parameters; if a value is a file, 
        /// the file is uploaded, otherwise it is stringified and sent in the request</param>
        public void SetParameters(Dictionary<string, object> parameters) {
            if (parameters != null) {
                foreach (var parameter in parameters) {
                    SetParameter(parameter.Key, parameter.Value);
                }
            }
        }

        /// <summary>
        /// Adds parameters to the request
        /// </summary>
        /// <param name="parameters">array of parameter names and values (parameters[2*i] is a name, 
        /// parameters[2*i + 1] is a value); if a value is a file, the file is uploaded, otherwise it 
        /// is stringified and sent in the request</param>
        public void SetParameters(Object[] parameters) {
            if (parameters != null) {
                for (int i = 0; i < parameters.Length - 1; i += 2) {
                    SetParameter(parameters[i].ToString(), parameters[i + 1]);
                }
            }
        }


        /// <summary>
        /// Posts the requests to the server, with all the cookies and parameters that were added
        /// </summary>
        /// <returns></returns>
        public WebResponse Post() {
            PostCookies();
            return DoPost();
        }

        /// <summary>
        /// Posts the requests to the server, with all the cookies and parameters that were added 
        /// before (if any), and with parameters that are passed in the argument
        /// </summary>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public WebResponse Post(Dictionary<string, object> parameters) {
            PostCookies();
            SetParameters(parameters);
            return DoPost();
        }

        /// <summary>
        /// Posts the requests to the server, with all the cookies and parameters that were added 
        /// before (if any), and with parameters that are passed in the argument
        /// </summary>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public WebResponse Post(Object[] parameters) {
            PostCookies();
            SetParameters(parameters);
            return DoPost();
        }

        /// <summary>
        /// Posts the requests to the server, with all the cookies and parameters that were added 
        /// before (if any), and with cookies and parameters that are passed in the arguments
        /// </summary>
        /// <param name="cookies"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public WebResponse Post(Dictionary<string, string> cookies, Dictionary<string, object> parameters) {
            SetCookies(cookies);
            PostCookies();
            SetParameters(parameters);
            return DoPost();
        }

        /// <summary>
        /// Posts the requests to the server, with all the cookies and parameters that were added 
        /// before (if any), and with cookies and parameters that are passed in the arguments
        /// </summary>
        /// <param name="raw_cookies"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public WebResponse Post(string raw_cookies, Dictionary<string, object> parameters) {
            SetCookies(raw_cookies);
            PostCookies();
            SetParameters(parameters);
            return DoPost();
        }

        /// <summary>
        /// Posts the requests to the server, with all the cookies and parameters that were added 
        /// before (if any), and with cookies and parameters that are passed in the arguments
        /// </summary>
        /// <param name="cookies"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public WebResponse Post(string[] cookies, Object[] parameters) {
            SetCookies(cookies);
            PostCookies();
            SetParameters(parameters);
            return DoPost();
        }

        /// <summary>
        /// Post the POST request to the server, with the specified parameter
        /// </summary>
        /// <param name="name"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public WebResponse Post(string name, Object value) {
            PostCookies();
            SetParameter(name, value);
            return DoPost();
        }
        
        private void Boundary() {
            Write("--");
            Write(_boundary);
        }

        protected void Connect() {
            if (_os == null)
                _os = _connection.GetRequestStream();
        }

        protected void Newline() {
            Connect();
            Write("\r\n");
        }
        
        private void PostCookies() {
            StringBuilder cookieList = new StringBuilder(_rawCookies);

            int count = 0;
            foreach (var cookie in _cookies) {

                cookieList.Append(cookie.Key + "=" + cookie.Value);

                if (count != _cookies.Count) {
                    cookieList.Append("; ");
                }
                count++;
            }
            if (cookieList.Length > 0) {
                _connection.Headers.Set("Cookie", cookieList.ToString());
            }
        }
        
        protected static string RandomString() {
            return Util.RandomAlphabetic(10);
        }

        protected void Write(string s) {
            Connect();
            byte[] bytes = Encoding.UTF8.GetBytes(s);
            _os.Write(bytes, 0, bytes.Length);
        }

        protected void Writeln(string s) {
            Connect();
            Write(s);
            Newline();
        }
        
        private void WriteName(string name) {
            Newline();
            Write("Content-Disposition: form-data; name=\"");
            Write(name);
            Write("\"");
        }

        /// <summary>
        /// Posts the requests to the server, with all the cookies and parameters that were added.
        /// </summary>
        /// <returns></returns>
        private WebResponse DoPost() {
            Boundary();
            Writeln("--");
            _os.Flush();
            _os.Close();

            return _connection.GetResponse();
        }

    }
}